package satir.framework.packaging;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * Provide operations of Extraction and Achieving.
 * @version 3.0 April 2012
 * @author Creative Family
 */
public class Packager {

    /** The filename. */
    private String filename;

    /**
     * Instantiates a new packager.
     * @param filename
     *            the filename
     */
    public Packager(final String filename) {
	this.filename = filename;
    }

    /**
     * Extract.
     * @param source
     *            the source
     * @param destination
     *            the destination
     * @throws PackagerArchieveFileDestinationNotFoundException
     *             the packager archive file destination not found exception
     * @throws PackagerExtractAchieveFileNotFoundException
     *             the packager extract achieve file not found exception
     * @throws PackagerExtractAchieveFileContentInvalidException
     *             the packager extract achieve file content invalid exception
     * @throws PackageExtractDestinactionWriteFailure
     *             the package extract destination write failure
     */
    public void extract(String source, String destination)
	    throws PackagerArchieveFileDestinationNotFoundException,
	    PackagerExtractAchieveFileNotFoundException,
	    PackagerExtractAchieveFileContentInvalidException,
	    PackageExtractDestinactionWriteFailure {
	final int buffer = 2048;

	// Correct the directory
	if (destination.isEmpty()
		&& !(destination.substring(destination.length() - 2,
			destination.length() - 1)).equals("/")) {
	    destination += "/";
	}
	if (source.isEmpty()
		&& !(source.substring(source.length() - 2, source.length() - 1))
			.equals("/")) {
	    source += "/";
	}

	// Create the directory
	try {
	    createDirectory(destination);
	} catch (IOException e1) {
	    throw new PackagerArchieveFileDestinationNotFoundException(
		    destination);
	}

	// Extract the file
	BufferedOutputStream dest = null;
	BufferedInputStream is = null;
	ZipEntry entry;
	ZipFile zipfile;
	try {
	    zipfile = new ZipFile(source + filename);
	} catch (IOException e1) {
	    throw new PackagerExtractAchieveFileNotFoundException(source
		    + filename);
	}
	Enumeration e = zipfile.entries();
	while (e.hasMoreElements()) {
	    entry = (ZipEntry) e.nextElement();
	    // System.out.println("Extracting: " + entry);
	    System.out.print(".");
	    try {
		is = new BufferedInputStream(zipfile.getInputStream(entry));
	    } catch (IOException e1) {
		throw new PackagerExtractAchieveFileContentInvalidException(
			source + filename, entry.toString());
	    }
	    int count;
	    byte[] data = new byte[buffer];
	    FileOutputStream fos;
	    try {
		fos = new FileOutputStream(destination + entry.getName());
	    } catch (FileNotFoundException e1) {
		throw new PackagerExtractAchieveFileContentInvalidException(
			source + filename, entry.toString());
	    }
	    dest = new BufferedOutputStream(fos, buffer);
	    try {
		while ((count = is.read(data, 0, buffer)) != -1) {
		    dest.write(data, 0, count);
		}
	    } catch (IOException e1) {
		throw new PackageExtractDestinactionWriteFailure(destination);
	    }
	    try {
		dest.flush();
	    } catch (IOException e1) {
		throw new PackageExtractDestinactionWriteFailure(destination);
	    }
	    try {
		dest.close();
	    } catch (IOException e1) {
		throw new PackageExtractDestinactionWriteFailure(destination);
	    }
	    try {
		is.close();
	    } catch (IOException e1) {
		throw new PackageExtractDestinactionWriteFailure(destination);
	    }
	}
    }

    /**
     * Achieve.
     * @param source
     *            the source
     * @param destination
     *            the destination
     * @throws PackagerArchieveFileDestinationNotFoundException
     *             the packager archieve file destination not found exception
     * @throws PackagerArchieveFileToBeArchievedNotFoundException
     *             the packager archieve file to be archieved not found
     *             exception
     * @throws PackagerArchieveZipFileWriteFailureException
     *             the packager archieve zip file write failure exception
     */
    public void achieve(String source, String destination)
	    throws PackagerArchieveFileDestinationNotFoundException,
	    PackagerArchieveFileToBeArchievedNotFoundException,
	    PackagerArchieveZipFileWriteFailureException {
	final int buffer = 2048;
	BufferedInputStream origin = null;
	FileOutputStream dest;

	// Correct the directory
	if (source.isEmpty()
		&& !(source.substring(source.length() - 2, source.length() - 1))
			.equals("/")) {
	    source += "/";
	}
	if (destination.isEmpty()
		&& !(destination.substring(destination.length() - 2,
			destination.length() - 1)).equals("/")) {
	    destination += "/";
	}

	// Create the directories
	try {
	    createDirectory(source);
	} catch (IOException e1) {
	    throw new PackagerArchieveFileDestinationNotFoundException(source);
	}
	try {
	    createDirectory(destination);
	} catch (IOException e1) {
	    throw new PackagerArchieveFileDestinationNotFoundException(
		    destination);
	}

	try {
	    dest = new FileOutputStream(destination + filename);
	} catch (FileNotFoundException e) {
	    throw new PackagerArchieveFileDestinationNotFoundException(
		    destination + filename);
	}
	ZipOutputStream out = new ZipOutputStream(
		new BufferedOutputStream(dest));
	// out.setMethod(ZipOutputStream.DEFLATED);
	byte[] data = new byte[buffer];
	// get a list of files from source directory
	File f = new File(source + ".");
	String[] files = f.list();

	for (int i = 0; i < files.length; i++) {
	    FileInputStream fi;
	    try {
		// System.out.println(source + files[i]);
		System.out.print(".");
		fi = new FileInputStream(source + files[i]);
	    } catch (FileNotFoundException e) {
		throw new PackagerArchieveFileToBeArchievedNotFoundException(
			files[i]);
	    }
	    origin = new BufferedInputStream(fi, buffer);
	    ZipEntry entry = new ZipEntry(files[i]);
	    try {
		out.putNextEntry(entry);
	    } catch (IOException e) {
		throw new PackagerArchieveZipFileWriteFailureException(
			destination + filename);
	    }
	    int count;
	    try {
		while ((count = origin.read(data, 0, buffer)) != -1) {
		    out.write(data, 0, count);
		}
	    } catch (IOException e) {
		throw new PackagerArchieveZipFileWriteFailureException(
			destination + filename);
	    }
	    try {
		origin.close();
	    } catch (IOException e) {
		throw new PackagerArchieveZipFileWriteFailureException(
			destination + filename);
	    }
	}
	try {
	    out.close();
	} catch (IOException e) {
	    throw new PackagerArchieveZipFileWriteFailureException(destination
		    + filename);
	}
    }

    /**
     * Creates the directory.
     * @param directory
     *            the directory
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void createDirectory(final String directory) throws IOException {
	File f = new File(directory);
	if (!f.exists()) {
	    f.mkdir();
	}
    }

    /**
     * The Class PackagerException.
     */
    public class PackagerException extends Exception {

	/**
	 * Instantiates a new packager exception.
	 * @param message
	 *            the message
	 */
	public PackagerException(final String message) {
	    super(message);
	}
    }

    /**
     * The Class PackagerExtractAchieveFileNotFoundException.
     */
    public class PackagerExtractAchieveFileNotFoundException extends
	    PackagerException {

	/**
	 * Instantiates a new packager extract achieve file not found exception.
	 * @param filename
	 *            the filename
	 */
	public PackagerExtractAchieveFileNotFoundException(final String filename) {
	    super("ERROR: The achieve file not found! Reference: destination["
		    + filename + "]");
	}
    }

    /**
     * The Class PackagerExtractAchieveFileContentInvalidException.
     */
    public class PackagerExtractAchieveFileContentInvalidException extends
	    PackagerException {

	/**
	 * Instantiates a new packager extract achieve file content invalid
	 * exception.
	 * @param achieveFilename
	 *            the achieve filename
	 * @param contentFilename
	 *            the content filename
	 */
	public PackagerExtractAchieveFileContentInvalidException(
		final String achieveFilename, final String contentFilename) {
	    super(
		    "ERROR: The content inside the achieve file is invalid! Reference: achieveFile["
			    + achieveFilename
			    + "], content["
			    + contentFilename
			    + "]");
	}
    }

    /**
     * The Class PackageExtractDestinactionWriteFailure.
     */
    public class PackageExtractDestinactionWriteFailure extends
	    PackagerException {

	/**
	 * Instantiates a new package extract destinaction write failure.
	 * @param destination
	 *            the destination
	 */
	public PackageExtractDestinactionWriteFailure(final String destination) {
	    super("ERROR: Destination write failure! Reference: destination["
		    + destination + "]");
	}
    }

    /**
     * The Class PackagerArchieveFileDestinationNotFoundException.
     */
    public class PackagerArchieveFileDestinationNotFoundException extends
	    PackagerException {

	/**
	 * Instantiates a new packager archieve file destination not found
	 * exception.
	 * @param destination
	 *            the destination
	 */
	public PackagerArchieveFileDestinationNotFoundException(
		final String destination) {
	    super(
		    "ERROR: The destination of archieve file does not exists! Reference: destination["
			    + destination + "]");
	}
    }

    /**
     * The Class PackagerArchieveFileToBeArchievedNotFoundException.
     */
    public class PackagerArchieveFileToBeArchievedNotFoundException extends
	    PackagerException {

	/**
	 * Instantiates a new packager archieve file to be archieved not found
	 * exception.
	 * @param filename
	 *            the filename
	 */
	public PackagerArchieveFileToBeArchievedNotFoundException(
		final String filename) {
	    super("ERROR: The file to be archieved not found! Reference: file["
		    + filename + "]");
	}
    }

    /**
     * The Class PackagerArchieveZipFileWriteFailureException.
     */
    public class PackagerArchieveZipFileWriteFailureException extends
	    PackagerException {

	/**
	 * Instantiates a new packager archieve zip file write failure
	 * exception.
	 * @param filename
	 *            the filename
	 */
	public PackagerArchieveZipFileWriteFailureException(final String filename) {
	    super(
		    "ERROR: The archieve file can not be write! Reference: filename["
			    + filename + "]");
	}
    }
}
